/*
 * Copyright (C) 2011 Deutsche Telekom, A.G.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Contributed by: Giesecke & Devrient GmbH.
 */

package org.simalliance.openmobileapi.service.security.arf;

import android.util.Log;

import org.simalliance.openmobileapi.internal.Util;
import org.simalliance.openmobileapi.service.Channel;
import org.simalliance.openmobileapi.service.ISmartcardServiceCallback;
import org.simalliance.openmobileapi.service.OpenLogicalChannelResponse;
import org.simalliance.openmobileapi.service.Terminal;
import org.simalliance.openmobileapi.service.security.ChannelAccess;
import org.simalliance.openmobileapi.service.security.arf.PKCS15.EF;
import org.simalliance.openmobileapi.service.security.gpac.dataobjects.AID_REF_DO;
import org.simalliance.openmobileapi.service.security.gpac.dataobjects.Hash_REF_DO;
import org.simalliance.openmobileapi.service.security.gpac.dataobjects.REF_DO;

import java.util.MissingResourceException;

/**
 * Provides high-level functions for SE communication
 */
public class SecureElement {

    public static final String TAG = "SmartcardService ACE ARF";
    public static final short SIM_IO = 1;
    public static final short SIM_ALLIANCE = 0;
    // Callback used during "Secure Element" communication
    private final ISmartcardServiceCallback mCallback = new ISmartcardServiceCallback.Stub() {
    };
    // Logical channel used for SE communication (optional)
    private Channel mArfChannel = null;
    // Handle to a built-in "Secure Element"
    private Terminal mTerminalHandle = null;
    // Arf Controller within the SCAPI handler
    private ArfController mArfHandler = null;
    // Interface for exchanging APDU commands
    private short mSEInterface = SIM_ALLIANCE;

    /**
     * Constructor
     *
     * @param arfHandler - handle to the owning arf controller object
     * @param handle     - handle to the SE terminal to be accessed.
     */
    public SecureElement(ArfController arfHandler, Terminal handle) {
        mTerminalHandle = handle;
        mArfHandler = arfHandler;
    }

    public short getSeInterface() {
        return mSEInterface;
    }

    public void setSeInterface(short seInterface) {
        mSEInterface = seInterface;
    }

    /**
     * Transmits ADPU commands
     *
     * @param cmd APDU command
     * @return Data returned by the APDU command
     */
    public byte[] exchangeAPDU(EF ef, byte[] cmd)
            throws SecureElementException {
        try {
            if (mSEInterface == SIM_IO) {

                return mTerminalHandle.simIOExchange(ef.getFileId(), ef.getFilePath(), cmd);
            } else {
                mTerminalHandle.getAccessControlEnforcer()
                        .checkCommand(mArfChannel, cmd);
                cmd[0] = Util.setChannelToClassByte(cmd[0], mArfChannel.getChannelNumber());
                return mTerminalHandle.transmit(cmd, 2, 0, 0, null);
            }
        } catch (Exception e) {
            throw new SecureElementException("Secure Element access error " + e.getLocalizedMessage());
        }
    }

    /**
     * Opens a logical channel to ARF Applet or ADF
     *
     * @param AID Applet identifier
     * @return Handle to "Logical Channel" allocated by the SE;
     * <code>0</code> if error occurred
     */
    public Channel openLogicalArfChannel(byte[] AID) {
        try {
            OpenLogicalChannelResponse rsp = mTerminalHandle.internalOpenLogicalChannel(AID, (byte) 0x00);
            if (rsp == null) {
                return null;
            }
            mArfChannel = new Channel(null, rsp.getChannel(), AID, rsp.getSelectResponse(), mCallback);
            setUpChannelAccess(mArfChannel);
            return mArfChannel;
        } catch (Exception e) {
            if (e instanceof MissingResourceException) {
                // this indicates that no channel is left for accessing the SE element
                Log.d(TAG, "no channels left to access ARF: " + e.getMessage());
                throw (MissingResourceException) e;
            } else {
                Log.e(TAG, "Error opening logical channel " + e.getLocalizedMessage());
            }
            mArfChannel = null;
            return null;
        }
    }

    /**
     * Closes a logical channel previously allocated by the SE
     */
    public void closeArfChannel() {
        try {
            if (mArfChannel != null) {
                mTerminalHandle.internalCloseLogicalChannel(mArfChannel.getChannelNumber());
                mArfChannel = null;
            }
        } catch (Exception e) {
            Log.e(TAG, "Error closing channel " + e.getLocalizedMessage());
        }
    }

    /**
     * Set up channel access to allow,
     * so that PKCS15 files can be read.
     *
     * @param channel
     */
    private void setUpChannelAccess(Channel channel) {
        // set access conditions to access ARF.
        ChannelAccess arfChannelAccess = new ChannelAccess();
        arfChannelAccess.setAccess(ChannelAccess.ACCESS.ALLOWED, "");
        arfChannelAccess.setApduAccess(ChannelAccess.ACCESS.ALLOWED);
        channel.setChannelAccess(arfChannelAccess);

    }

    public byte[] getRefreshTag() {
        if (mArfHandler != null) {
            return mArfHandler.getAccessRuleCache().getRefreshTag();
        }
        return null;
    }

    public void setRefreshTag(byte[] refreshTag) {
        if (mArfHandler != null) {
            mArfHandler.getAccessRuleCache().setRefreshTag(refreshTag);
        }
    }

    public void putAccessRule(AID_REF_DO aid_ref_do, Hash_REF_DO hash_ref_do, ChannelAccess channelAccess) {

        REF_DO ref_do = new REF_DO(aid_ref_do, hash_ref_do);
        mArfHandler.getAccessRuleCache().putWithMerge(ref_do, channelAccess);
    }

    public void resetAccessRules() {
        this.mArfHandler.getAccessRuleCache().reset();
    }

    public void clearAccessRuleCache() {
        this.mArfHandler.getAccessRuleCache().clearCache();
    }
}
